﻿//Copyright (c) 2010, Andreas Grünwald
//All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;

#if SILVERLIGHT
using System.IO.IsolatedStorage;
#endif

namespace yaTDL.Core.IO
{
    public class XMLFileHandler:FileHandler
    {

        /// <summary>
        /// Initializes a new instance of XMLFileHandler
        /// </summary>
        public XMLFileHandler() { }
#if !SILVERLIGHT
        /// <summary>
        /// Initializes a new instance of XMLFileHandler
        /// </summary>
        /// <param name="enableDefaultFileMonitoring">Specifies whether changes in the default file will automatically be applied to the data stored in the appliation.</param>
        public XMLFileHandler(bool enableDefaultFileMonitoring) : base(enableDefaultFileMonitoring) { } 
#endif    
        
        /// <summary>
        /// Saves the collection of projects to a file at the indicated location
        /// </summary>
        /// <param name="projects">The Collection that contains all Projects that will be written to the file.</param>
        /// <param name="tasks">The Collection of Tasks that will be stored in the file.</param>
        /// <param name="changes">The Collection of all Changes that will be stored in the file.</param>
        /// <param name="fileName">
        /// .net: Specifies the full filename (including the path) of the file the Projects will be written to.
        /// Silverlight: Specifies the filename in IsolatedStorage of the file the Projects will be written to.
        /// </param>
        /// <remarks>
        /// Implements a abstract method inherited from <see cref="FileHandler"/>
        /// </remarks>
        public override void SaveData(Collection<Project> projects, Collection<Task> tasks, Collection<Change> changes, string fileName)
        {
            if (!EnableWriting) { return; }

#if !SILVERLIGHT
            if (defaultFile_FileSystemWatcher != null)
            { defaultFile_FileSystemWatcher.EnableRaisingEvents = false; }
            
            StreamWriter sw = new StreamWriter(fileName);   
#endif
#if SILVERLIGHT

            IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication();
            IsolatedStorageFileStream isoStream = null;

            try
            {
                isoStream = store.OpenFile(fileName, FileMode.Create, FileAccess.Write);
            }
            catch (Exception)
            {
                SaveProjects(projects, fileName);
                return;
            }

            StreamWriter sw = new StreamWriter(isoStream);       
#endif
            
            XDocument xDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"), new XComment("data file used by yaTDL or 1.1.0.7 or later"));
            xDoc.Add(new XElement("Data"));

            if (projects != null)
            {
                xDoc.Element("Data").Add(new XElement("Projects"));
                foreach (Project p in projects)
                {
                    XElement projectNode = new XElement("Project");
                    projectNode.Add(new XElement("Name") { Value = p.Name });
                    projectNode.Add(new XElement("Id") { Value = p.Id });
                    projectNode.Add(new XElement("Collapsed") { Value = p.Collapsed.ToString() });
                    projectNode.Add(new XElement("Indent") { Value = p.Indent.ToString() });
                    projectNode.Add(new XElement("LastEdited") { Value = p.LastEdited.ToString() });
                    projectNode.Add(new XElement("Order") { Value = p.Order.ToString() });
                    projectNode.Add(new XElement("UserId") { Value = p.UserId });

                    xDoc.Element("Data").Element("Projects").Add(projectNode);
                }
            }

            if (tasks != null)
            {
                xDoc.Element("Data").Add(new XElement("Tasks"));
                foreach (Task t in tasks)
                {
                    XElement taskNode = new XElement("Task");
                    taskNode.Add(new XElement("Collapsed") { Value = t.Collapsed.ToString() });
                    taskNode.Add(new XElement("Content") { Value = t.Content });
                    taskNode.Add(new XElement("DueDate") { Value = t.DueDate.ToString() });
                    taskNode.Add(new XElement("Id") { Value = t.Id });
                    taskNode.Add(new XElement("Indent") { Value = t.Indent.ToString() });
                    taskNode.Add(new XElement("InHistory") { Value = t.Completed.ToString() });
                    taskNode.Add(new XElement("LastEdited") { Value = t.LastEdited.ToString() });
                    taskNode.Add(new XElement("Order") { Value = t.Order.ToString() });
                    taskNode.Add(new XElement("Priority") { Value = t.Priority.ToString() });
                    taskNode.Add(new XElement("ProjectId") { Value = t.ProjectId });
                    taskNode.Add(new XElement("Recurrence", new XElement("DateString") { Value = t.Recurrence.AdvancedRepeat }, new XElement("Repeat") { Value = t.Recurrence.Repeat.ToString() }));
                    taskNode.Add(new XElement("UserId") { Value = t.UserId });

                    xDoc.Element("Data").Element("Tasks").Add(taskNode);
                }
            }

            if (changes != null)
            {
                xDoc.Element("Data").Add(new XElement("Changes"));
                foreach (Change c in changes)
                {
                    XElement changeNode = new XElement("Change");
                    changeNode.Add(new XElement("ChangedItemType") { Value = c.ChangedItemType.ToString() });
                    changeNode.Add(new XElement("ChangeTime") { Value = c.ChangeTime.ToString() });
                    changeNode.Add(new XElement("Handled") { Value = c.HandledInSync.ToString() });
                    changeNode.Add(new XElement("ItemId") { Value = c.ItemId.ToString() });
                    changeNode.Add(new XElement("Kind") { Value = c.Kind.ToString() });
                    changeNode.Add(new XElement("PropertyName") { Value = c.PropertyName });

                    xDoc.Element("Data").Element("Changes").Add(changeNode);
                }
            }

            sw.Write(xDoc.ToString());
            sw.Close();

#if SILVERLIGHT
            isoStream.Close();
#else
            if (defaultFile_FileSystemWatcher != null)
            { defaultFile_FileSystemWatcher.EnableRaisingEvents = true; }
#endif
        }

        /// <summary>
        /// Reads all Projects from the specified file.
        /// </summary>
        /// <param name="fileName">The full, absolute path of the file to be read.</param>
        /// <returns>Returns a Collection of all Projects found in the File.</returns>
        /// <remarks>
        /// Implements a abstract method inherited from <see cref="FileHandler"/>
        /// </remarks>
        public override Collection<Project> GetProjects(string fileName)
        {
            XDocument xmlDoc = getXMLDocument(fileName);
            if (xmlDoc == null) { return new Collection<Project>(); }

            var projectQuery = from project in xmlDoc.Element("Data").Element("Projects").Elements()
                                select new Project
                                {
                                    Name = project.Element("Name").Value,
                                    Id = project.Element("Id").Value,
                                    Collapsed = Int32.Parse(project.Element("Collapsed").Value),
                                    Indent = Int32.Parse(project.Element("Indent").Value),
                                    LastEdited = DateTime.Parse(project.Element("LastEdited").Value),
                                    Order = Int32.Parse(project.Element("Order").Value),
                                    UserId = project.Element("UserId").Value
                                };            

            return new Collection<Project>(projectQuery.ToList<Project>());

        }

        /// <summary>
        /// Reads all Tasks from the specified file.
        /// </summary>
        /// <param name="fileName">The full, absolute path of the file to be read.</param>
        /// <returns>Returns a Collection of all Tasks found in the file.</returns>
        /// <remarks>
        /// Implements a abstract method inherited from <see cref="FileHandler"/>
        /// </remarks>
        public override Collection<Task> GetTasks(string fileName)
        {
            XDocument xmlDoc = getXMLDocument(fileName);
            if (xmlDoc == null) { return new Collection<Task>(); }

            var tasksQuery = from task in xmlDoc.Element("Data").Element("Tasks").Elements()
                             select new Task
                             {
                                 Collapsed = Int32.Parse(task.Element("Collapsed").Value),
                                 Content = task.Element("Content").Value,
                                 DueDate = DateTime.Parse(task.Element("DueDate").Value),
                                 Id = task.Element("Id").Value,
                                 Indent = Int32.Parse(task.Element("Indent").Value),
                                 Completed = bool.Parse(task.Element("InHistory").Value),
                                 LastEdited = DateTime.Parse(task.Element("LastEdited").Value),
                                 Order = Int32.Parse(task.Element("Order").Value),
                                 Priority = Int32.Parse(task.Element("Priority").Value),
                                 ProjectId = task.Element("ProjectId").Value,
                                 UserId = task.Element("UserId").Value,
                                 Recurrence = new Recurrence() { AdvancedRepeat = task.Element("Recurrence").Element("DateString").Value, 
                                     Repeat = Int32.Parse(task.Element("Recurrence").Element("Repeat").Value) }
                             };

            return new Collection<Task>(tasksQuery.ToList<Task>());
        }

        /// <summary>
        /// Reads all Changes from the specified file.
        /// </summary>
        /// <param name="fileName">The full, absolute path of the file to be read.</param>
        /// <returns>Returns a Collection of all Chnages found in the file.</returns>
        /// <remarks>
        /// Implements a abstract method inherited from <see cref="FileHandler"/>
        /// </remarks>
        public override Collection<Change> GetChanges(string fileName)
        {
            XDocument xmlDoc = getXMLDocument(fileName);
            if (xmlDoc == null) { return new Collection<Change>(); }

            var changeQuery = from change in xmlDoc.Element("Data").Element("Changes").Elements()
                              select new Change
                              {
                                  ChangedItemType = Type.GetType(change.Element("ChangedItemType").Value),
                                  ChangeTime = DateTime.Parse(change.Element("ChangeTime").Value),
                                  HandledInSync = boolParse(change.Element("Handled").Value),
                                  ItemId = change.Element("ItemId").Value,
                                  Kind = (ChangeKind) Enum.Parse(typeof(ChangeKind), change.Element("Kind").Value),
                                  PropertyName = change.Element("PropertyName").Value,                                  
                              };

            List<Change> changeList = changeQuery.ToList<Change>();

            return new Collection<Change>(changeList);
        }


        private bool boolParse(string value)
        {
            bool result = bool.Parse(value);
            return result;
        }

        /// <summary>
        /// Returns the default file name for file that will be used by OpenProjects() and SaveProjects() if no other filename 
        /// has been specified. The file should be located in defaultDirectory.
        /// </summary>
        /// <remarks>
        /// Implements a abstract property inherited from <see cref="FileHandler"/>
        /// </remarks>
        public override string DefaultFileName
        {
            get
            {
#if SILVERLIGHT
                return @"\TaskData.xml";
#else
                return DefaultDirectory + @"\TaskData.xml";
#endif
            }
        }     


#if !SILVERLIGHT

        /// <summary>
        /// Gets the DateTime of the specified file's last edit.
        /// </summary>
        /// <param name="fileName">The file to be read.</param>
        /// <returns>Returns a DateTime indicating when the specified file has been editet the last time.</returns>
        public override DateTime GetDateOfLastEdit(string filePath)
        {
            return new FileInfo(filePath).LastWriteTime;
        }
#endif



        private XDocument getXMLDocument(string fileName)
        {
#if SILVERLIGHT
            IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication();

            if (!store.FileExists(fileName)) { return projects; }

            IsolatedStorageFileStream isoStream = store.OpenFile(fileName, FileMode.Open);

            XmlReader xmlReader = XmlReader.Create(isoStream);

            XDocument xmldoc = XDocument.Load(xmlReader);

#else
            if (!File.Exists(fileName)) { return null; }

            XmlReader xmlReader = XmlReader.Create(fileName, new XmlReaderSettings());

            XDocument xmldoc = XDocument.Load(xmlReader);
#endif

            xmlReader.Close();

            return xmldoc;
        }


        #region Saved Searches

        public string SavedSearchesFileName
        {
            get
            {
                return Path.Combine(DefaultDirectory, "SavedSearches.xml");
            }
        }

        public IEnumerable<SavedSearch> GetSavedSearches()
        {
            if (!File.Exists(SavedSearchesFileName)) return new List<SavedSearch>();

            XDocument xDoc = XDocument.Parse(File.ReadAllText(SavedSearchesFileName));

            var searches = from search in xDoc.Descendants("SavedSearch")
                           select new SavedSearch() { Name = search.Attribute("name").Value, SearchTerm = search.Value };

            return searches.ToList<SavedSearch>();

        }

        public void SaveSavedSearches(IEnumerable<SavedSearch> searches)
        {
            XDocument xDoc = new XDocument();
            XElement searchesXElement = new XElement("savedSearches");

            if (searches != null)
            {

                foreach (SavedSearch item in searches)
                {
                    XElement s = new XElement("SavedSearch");
                    s.Add(new XAttribute("name", item.Name));
                    s.Value = item.SearchTerm;
                    searchesXElement.Add(s);
                }
            }


            xDoc.Add(searchesXElement);

            using (StreamWriter sw = new StreamWriter(SavedSearchesFileName))
            {
                sw.Write(xDoc.ToString());
                sw.Close();
            }
        }


        #endregion

    }
}
